﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using FinMkts.DataProvider.Internal;
using FinMkts.Messaging;
using FinMkts.Messaging.Pgm;

namespace FinMkts.Price.Simulator
{
    public class MarketDataSimulator
    {
        string _topicPrefix;
        private Thread _thread;
        private readonly Random _random;
        private int _messagesSent;

        public string ProviderCode { get; set; }
        
        public IEnumerable<string> DataCodes { get; set; }

        public TimeSpan MinPriceDelay { get; set; }

        public TimeSpan MaxPriceDelay { get; set; }

        [Import]
        public Publisher Publisher { get; set; }

        [Import]
        public InternalPriceSerializer PriceSerializer { get; set; }

        public MarketDataSimulator()
        {
            MinPriceDelay = TimeSpan.FromSeconds(0);
            MaxPriceDelay = TimeSpan.FromSeconds(0.01);
            _random = new Random(Environment.TickCount);
        }

        public void Start()
        {
            if (string.IsNullOrWhiteSpace(ProviderCode)) throw new InvalidOperationException("ProviderCode must be set");
            if (DataCodes == null) throw new InvalidOperationException("DataCodes must be set");

            _messagesSent = 0;
            _topicPrefix = "Price." + ProviderCode + ".";

            Publisher.Start();

            var markets = from dataCode in DataCodes ?? new string[0]
                          select new MarketState { ListingCode = dataCode, Mid = 100, };

            _thread = new Thread(SimulatorLoop) { IsBackground = true };
            _thread.Start(markets);

            var pgmPublisher = Publisher as PgmPublisher;
            if (pgmPublisher != null)
            {
                BinarySize last = BinarySize.Zero;
                pgmPublisher.SenderStatistics.Subscribe(stats =>
                                           {
                                               Console.WriteLine("{0:N0} messages, total sent {1}, {2:N1} Mbps", 
                                                   _messagesSent, stats.ClientDataSent-last, stats.RatePerSecLast.MegaBits);
                                               Interlocked.Exchange(ref _messagesSent, 0);
                                               last = stats.ClientDataSent;
                                           }, ex => Console.WriteLine(ex));
            }
        }

        void SimulatorLoop(object state)
        {
            var markets = (IEnumerable<MarketState>) state;
            var byDueTime = new SortedDictionary<DateTime, List<MarketState>>();
            foreach (var marketState in markets)
            {
                Add(marketState, byDueTime);
            }

            while (true)
            {
                var first = byDueTime.First();
                //Wait(first.Key);
                byDueTime.Remove(first.Key);
                foreach (var marketState in first.Value)
                {
                    InternalPrice price = GetRandomPrice(marketState);
                    Publish(marketState, price);
                    Add(marketState, byDueTime);
                }
            }
        }

        private void Add(MarketState marketState, SortedDictionary<DateTime, List<MarketState>> byDueTime)
        {
            var dueTime = DateTime.UtcNow; //+ GetRandomMSDelay(_random);
            List<MarketState> list;
            if (!byDueTime.TryGetValue(dueTime, out list))
            {
                list = new List<MarketState>();
                byDueTime.Add(dueTime, list);
            }
            list.Add(marketState);
        }

        private void Wait(DateTime dueTimeUTC)
        {
            var sleepFor = dueTimeUTC - DateTime.UtcNow;
            if (sleepFor > TimeSpan.Zero)
            {
                Thread.Sleep(sleepFor);
            }
        }

        TimeSpan GetRandomMSDelay(Random rnd)
        {
            var min = (int)MinPriceDelay.TotalMilliseconds;
            int max = (int)MaxPriceDelay.TotalMilliseconds;
            return TimeSpan.FromMilliseconds(rnd.Next(min, max));
        }

        private void Publish(MarketState marketState, InternalPrice p)
        {
            var serialized = PriceSerializer.Serialize(p);
            var message = new AddressedMessage {Topic = _topicPrefix + marketState.ListingCode, Body = serialized};
            Publisher.Publish(message);
            Interlocked.Increment(ref _messagesSent);
            //Console.WriteLine("{0}: Bid={1:N2} Ask={2:N2}", message.Topic, p.Bid, p.Ask);
        }

        private InternalPrice GetRandomPrice(MarketState marketState)
        {
            var lastTrade = marketState.Mid;
            int change = _random.Next(-10, +10);
            marketState.Mid += (change/10.0);
            return new InternalPrice
                       {
                           Bid = marketState.Mid - 1,
                           Ask = marketState.Mid + 1,
                           ListingCode = marketState.ListingCode,
                           LastTradePrice = lastTrade,
                       };
        }
    }

    class MarketState
    {
        public string ListingCode { get; set; }
        public double Mid { get; set; }
    }
}
